跳到主要内容

Create by fall on 12 Jun 2022 Recently revised in 17 Apr 2023

二进制对象

总共有五个大对象,BlobArrayBufferFileFileReaderTypedArray

  • BlobArrayBufferFile 可以归为一类,它们都是数据;
  • FileReaderTypedArray 算是工具,用来读取对应数据;

Blob & File

Blob

Blob 全称是 binary large objectBlob 对象就是一个包含有只读原始数据的类文件对象,通俗讲就是不可修改的二进制文件。在数据库管理系统中,将二进制数据存储为一个单一个体的集合。Blob 通常是影像、声音或多媒体文件。

上传下载通常都是 Blob 对象

创建 Blob 对象

// 第一个参数可以是 ArrayBufferView / Blob / ArrayBuffer / DOMString 等对象构成的数组,其中 DOMString 会被编译为 utf-8
let blobParts = ['<html><h2>Hello Fall</h2></html>']
let options = {type : 'text/html'}
// options:一个可选对象,包含两个属性
// type:默认为 '' 表明放到 blob 中的数组内容的 MIME 类型(见注释)
// endings:用于指定包含行结束符的 `n` 字符串如何被写入,默认为 "transparent" 保持 blob 中的格式不变,可选为 "native",代表行结束符,会被更改为适合宿主操作系统文件系统的换行符
let myBlob = new Blob(blobParts,options)
// 此时 myBlob 是一个对象,拥有 size, type 两个属性
console.log(myBlob.size + " bytes"); // myBlob.size 用字节表示的数据大小(只读)
// 37 bytes
console.log("MIME type is " + myBlob.type); // myBlob.type 表示 MIME 类型(只读)
// MIME 类型为 text/html

MIME:Multipurpose Internet Mail Extensions:多用途互联网邮件扩展类型,用来区分文件的种类。

Blob 上面的方法

// Blob 对象是不可改变的,但是可以通过 slice 进行分割
// start:分割起始点 end:分割终点 contentType:新的MIME类型
Blob.prototype.slice(start, end ,contentType)
// 返回一个能读取 Blob 内容的 ReadableStream
Blob.prototype.stream()
// 返回一个 Promise 对象,且包含 blob 所有内容的
Blob.prototyoe.text()
// 转换为 arrayBuffer 异步转换返回一个 Promise 对象
blob.arrayBuffer().then((res) => {
console.log(res);
});

File

File 对象继承自 Blob,也是二进制对象,通常用在 <input type='file'> 选择的 FileList 对象。或者是拖拽产生的 DataTransfer 对象。

var oneFile = new File(array,name,options)
// array 数据构成的数组
// name 文件名
// options 设置一些属性,Type 属性,lastModified
// File 对象上的属性
oneFile.name// 文件名
oneFile.size// 文件大小
oneFile.lastModified // 最后修改时间的时间戳
oneFile.lastModifiedDate // 最后修改时间的 Date 对象
oneFile.type // MIME类型

方法继承自 Blob

File.prototype.slice(start, end ,contentType)
File.prototype.stream()
File.prototyoe.text()
File.arrayBuffer().then((res) => {
console.log(res);
});

Base64

Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法,它常用于在处理文本数据的场合,表示、传输、存储一些二进制数据,包括 MIME 的电子邮件及 XML 的一些复杂数据。

浏览器内置了两个方法进行字符串的 Base64 编码和解码

btoa => Binary to ASCII

const str = 'Fall'
// 编码
const b = window.btoa(str)
b // "RmFsbA=="
// 解码
const str2 = window.atob(b)
str2 // "Fall"

用途和特性:

  • 将较小的图片转为 DataURL 字符串,直接嵌入网页文本中,同 HTML 或者 JS 文件一并传输,减少请求次数。
  • 对数据进行加密(不安全,太容易破解了)
  • 编码后体积变大,至少增加 33%,如果过是一个字节,至少变为三个字节
  • 编码和解码需要消耗额外工作量

DataURL

URI 是 URL 的一个父类,包括 URL 和 URN URI(Uniform Resource Identifier):统一资源标识符 URN(Uniform Resource Name):统一资源名称 URL(Uniform Resource Locator):统一资源定位符 DataURL 是一种特殊的 base64 格式,用于表示数据的 URL。可以作为 <img> 标签的 src 属性进行使用。

Data URLs 的数据由四部分组成
前缀:[data:] 指示数据类型的 MIME 类型[<mediatype>]
如果非文本则为可选的 base64 标记:[;base64]、数据本身:,<data>
其中 mediatype 可以为 "image/jpeg" 表示 JPEG 图像文件。
data:[<mediatype>][;base64],<data>
示例:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...

ObjectURL

ObjectURL 将一个 file 或者 Blob 类型的数据转换为 UTF-16 的字符串,存在当前操作的 document 下,并存储在内存中。

通过该 URL 可以读取到保存的文件。

类似这样一个链接
blob:http://localhost:3000/53acc2b6-f47b-450f-a390-bf0665e04e59
// 生成 blob url
const blob = new Blob(['ddididid'],{type:'text/plain'})
const file = new File(['fall'],{type:'text/plain'})
const fileURL = URL.createObjectURL(file) // 也可以放入 blob
// 生成的 URL 存储了一个 URL → Blob 映射。
// 数据存储后会常驻内存,只有页面 unload() 事件触发,或者使用手动清除内存占用
URL.revokeObjectURL(fileURL) // 手动清除 document 中的 blob
// URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象。

前端下载经常会使用这些内容

const downloadFile = async (params, fileName) => {
// 我们使用axios设置接口返回类型 responseType: "blob", 所以这里从后端返回的是blob。
const results = await download(params);
const a = document.createElement("a");
a.download = fileName + ".xlsx";
// 生成blob url。这里可以使用Blob对象或者File对象
a.href = URL.createObjectURL(results);
document.body.appendChild(a);
a.click();
// 释放内存
URL.revokeObjectURL(a.href);
document.body.removeChild(a);
};

二进制操作

对象关系

BlobArrayBuffer 的关系

  • ArrayBuffer 是底层的二进制数据,可以借助工具进行读写。
  • Blob 对象是不可变的,可以接受一个 ArrayBuffer 作为参数生成的一个 Blob 对象,相当于将 ArrayBuffer 封装为一个整体。拿到的是一个整体,你能看到大小,类型,但是不能看到细节。
  • BlobArrayBuffer 对象之间是可以相互转化的:

ArrayBufferTypedArray 的关系

  • ArrayBuffer 表示纯 01 构成的串,不知道第一个元素和第二个元素需要如何分割
  • TypedArray 九中类型的构造函数,根据九种类型,去分割 ArrayBuffer 的纯 01 串

FileReader

FileReader 可以异步读取存储在用户计算机上(或者原始数据缓冲区)的 BlobFile 文件,读取为 ArrayBuffer、base64 字符串、文本。

  • fileReader.readAsArrayBuffer 生成的是 ArrayBuffer
  • fileReader.readAsDataURL 生成的是 base64 字符串
  • fileReader.readAsText 生成的是一个文本

使用

// 创建一个 fileReader 对象
const fileReader = new FileReader()
// 属性
fileReader.error // 表示在读取过程中出现错误
fileReader.result // 返回文件的内容,读取完成后,该属性才会生效,result 会根据不同的读取方式有不同的结果
fileReader.readyState // 表示当前文件的读取状态,0:没有加载任何数据,1:数据正在被加载,2:已经完成全部读取请求

方法

// 无论读取成功与否,并不会返回读取的结果,结果会被存储在 fileReader.result 属性中
// 所以一般读取成功后通过 onload 事件,输出 fileReader.result
fileReader.abort() // 终止读取操作,readyState 为 2
fileReader.readAsArrayBuffer() // 将读取的内容转换为 ArrayBuffer
fileReader.readAsBinaryString()
fileReader.readAsDataURL() // 将数据转化为 Base64 的 data url
fileReader.readAsText() // 将二进制数据读取并编码为字符串形式

事件

fileReader.onabort // 读取操作中断时触发
fileReader.onerror // 当出现错误时触发
fileReader.onload // 读取操作完成时触发
fileReader.onloadstart // 读取操作开始时触发
fileReader.onloadend // 读取操作结束时触发,成功或者失败
fileReader.onprogress // 处理 progrress 事件,读取 Blob 时触发

使用

// 读取二进制文件
const blob = new Blob(['hello','everyday'],{type:'text/plain'})
const fileReaderOne = new FileReader()
fileReader.readAsDataURL(blob)
fileReader.onload=()=>{
console.log(fileReader) // 成功后 fileReader.result 是想要的结果
}
<!-- 读取文件 -->
<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>
<script>
const loadFile = function(event) {
const reader = new FileReader();
// readAsDataURL 会把文件读取为 base64 的 dataURL
reader.readAsDataURL(event.target.files[0]); // 或者通过 node.files 获取文件
reader.onload = function(){
const output = document.querySelector('#output')
output.src = reader.result; // reader.result 即读取后的内容
};
};
</script>

ArrayBuffer

ArrayBuffer 对象用于表示通用的,固定长度的原始二进制数据。

它是内存上的一段二进制数据,你不能直接操纵 ArrayBuffer 的内容,而是需要创建一个类型化数组对象(TypeArray)或 DataView 对象进行操作,该对象以特定格式表示缓冲区,并使用该对象读取和写入缓冲区的内容。

Blob 作为一个整体的文件,适合用于传输。只有关注文件的细节修改一部分的数据,才会用到 ArrayBuffer

// Blob 和 ArrayBuffer 之间相互转化


TypedArray

一组构造函数,一共包含九种类型,每一种都是一个构造函数。

TypedArray 的构造函数接受三个参数,第一个 ArrayBuffer(其实还可以是数组、视图这里不细说)对象,第二个视图开始的字节号(默认 0),第三个视图结束的字节号(默认直到本段内存区域结束)。

名称占用字节描述
Int8Array18 位有符号整数
Uint8Array18 位无符号整数
Uint8ClampedArray18 位无符号整型固定数组(数值在0~255之间)
Int16Array216 位有符号整数
Uint16Array216 位无符号整数
Int32Array432 位有符号整数
Uint32Array432 位无符号整数
Float32Array432 位 IEEE 浮点数
Float64Array864 位 IEEE 浮点数
const buffer =  [ 126, 226 ] // 可以是数组、视图、ArrayBuffer
const uint8 = new Uint8Array(buffer); // 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位)。这称为 “8 位无符号整数”。
const uint16 = new Uint16Array(buffer); // 将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。
const uint32 = new Uint32Array(buffer);// 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。
const float64 = new Float64Array(buffer);// 将每 8 个字节视为一个 5.0x10-324 到 1.8x10308 之间的浮点数。

DataView

更灵活的视图,DataView 视图支持除 Uint8ClampedArray 以外的八种 TypedArray 类型。

可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口

// 通过传入 buffer 来实现对二进制的读取
const buffer = new ArrayBuffer(8); // 创建八个字节的 ArrayBuffer
const dataView1 = new DataView(buffer);
console.log(dataView1);
dataView1.setInt8(0, 1); // 0 是开始位子 存入1
dataView1.getInt8(0);// 1
view1.setInt8(1, 2); // 1 是开始位子 跳过 8 个位 存入2
dataView1.getInt8(1);// 1
// 读取第二个字节的时候 二进制位 0000 0010 为2
// 通过 16 进制进行读取
dataView1.getUint16() // 258
// 实际上读取的是 0000 0001 0000 0010

互相转换

转换一张图

image

图片来自文章:js一张图搞定arrayBuffer/Blob/File/fileReader/canvas/base64 的各种转换操作以及文件上传

Blob 和 File

// File 转换为 Blob
let file = new File(['文件'],'fileName.txt',{type:'text/plain'})
let blob = new Blob([file],{type:file.type})

// Blob 转化为 File
let blob = new Blob(['blob文件',{type:'text/plain'}])
let oneFile = new File([Blob],'fileName',{type:blob.type})

转为 base64

也就是转换为 DataURL

// File / Blob 转为 base64
function toDataURL(blob,callBack){
let reader = new FileReader()
reader.readAsDataURL(blob)
reader.onload = function (e){callBack(e.target.result)}
}
const blob = new Blob(['hello','fall'],{type:'text/plain'})
const file = new File(['byte','me'],'allright')
toDataURL(blob,(result)=>{
console.log(result)
})
toDataURL(file,(result)=>{
console.log(result)
})

img 转为 Base64

const imgToBase64 = (imgURL)=>{
let image = new Image()
image.src = imgURL
return new Promise(resolve=>{
image.onload=()=>{
let canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
let context = canvas.getContext('2d')
context.drawImage(image,0,0,image.width,image.height)
let dataURL = canvas.toDataURL('image/png')
resolve(dataURL)
}
})
}
imgToBase64("../vue2/src/assets/logo.png").then((res) => {
console.log(res);
});

Base64 转换

转为 Blob

function dataURLtoBlob(dataurl) {
// `data:[<mediatype>][;base64],<data>`
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}

转为 File

function dataURLtoFile(dataurl, filename) {
//将base64转换为文件
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
}

Blob 和 ArrayBuffer

将二进制内容转换为可以读写的 ArrayBuffer

const name = 'fall'
const blob = new Blob(name,{type:'text/plain'})

function readBlob (blob, type) {
  return new Promise(resolve => {
    const reader = new FileReader()
    reader.readAsArrayBuffer(blob)
    reader.onload = function (e) {
      resolve(e.target.result)
    }
  })
}

readBlob(blob).then(res=>{
new Unit8Array(res)
})

使用场景

下载文件

生成二进制文件,然后下载

const download = (fileName, blob) => {
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
link.remove();
URL.revokeObjectURL(link.href);
};
const downloadBtn = document.querySelector("#downloadBtn");
downloadBtn.addEventListener("click", (event) => {
  const fileName = "blob.txt";
  const myBlob = new Blob(["一文彻底掌握 Blob Web API"], { type: "text/plain" });
  download(fileName, myBlob);
});

生成 DataURL,然后下载

function downloadJSON(downloadData){
let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(downloadData));
let link = document.createElement('a')
link.setAttribute("href", dataStr);
link.setAttribute("download", "文件名.json")
link.click();
link.remove();
}
downloadJSON({
name:"Fall",
age:"23"
})

图片压缩

在前端要实现图片压缩,我们可以利用 Canvas 对象提供的 toDataURL() 方法,该方法接收 typeencoderOptions 两个可选参数。

// compress.js
const MAX_WIDTH = 800; // 图片最大宽度
const MAX_HEIGHT = 800; // 图片最大高度

// 分别传入 DataURL,图片质量,最高为 100,mime 类型
function compressImg(base64, quality, mimeType) {
  let canvas = document.createElement("canvas");
  let img = document.createElement("img");
  img.crossOrigin = "anonymous";
  return new Promise((resolve, reject) => {
    img.src = base64
    img.onload = () => {
      let targetWidth, targetHeight;
      if (img.width > MAX_WIDTH) {
        targetWidth = MAX_WIDTH;
        targetHeight = (img.height * MAX_WIDTH) / img.width;
      } else {
        targetWidth = img.width;
        targetHeight = img.height;
      }
      canvas.width = targetWidth;
      canvas.height = targetHeight;
      let ctx = canvas.getContext("2d");
      ctx.clearRect(0, 0, targetWidth, targetHeight); // 清除画布
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      let imageData = canvas.toDataURL(mimeType, quality / 100);
      resolve(imageData);
    }
  })
}

生成 PDF

import jspdf from 'jspdf'
function generatePdf() {
const js2pdf = new jsPDF();
js2pdf.text("Hello semlinker!", 66, 88);
const blob = new Blob([js2pdf.output()], { type: "application/pdf" });
blob.text().then((blobAsText) => {
console.log(blobAsText);
});
}

预览图片

FileReader API,我们也可以方便的实现图片本地预览功能,具体代码如下:

<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>
<script>
const loadFile = function(event) {
const reader = new FileReader();
// readAsDataURL 会把文件读取为 base64 的 dataURL
reader.readAsDataURL(event.target.files[0]);
reader.onload = function(){
const output = document.querySelector('#output')
output.src = reader.result;
};
};
</script>

检查文件类型

对于不同类型的文件,初始的几个字节内容都是固定的。

文件都是三十二位,二进制存储。比如说,D4 就是 1 Byte,也就是 8 Bit。

文件类型文件后缀魔数
JPEGjpg/jpeg0xFF D8 FF
PNGpng0x89 50 4E 47 0D 0A 1A 0A
GIFgif0x47 49 46 38(GIF8)
BMPbmp0x42 4D
PDFpdf0x25 50 44 46 对应的字符串为(%PDF)

问题来了,一个文件,开头是 89 50 4E 47,那么它是什么类型文件?查表可知:png

function readBuffer(file,start=0,end=2){
return new Promise((resolve,reject)=>{
const reader = new FileReader()
reader.onerror = rejcet
reader.onload=()=>{
resolve(reader.result)
}
reader.readAsArrayBuffer(file.slice(start,end))
})
}
function check(headers){
return (buffers,options={offset:0})=>headers.every((header,index)=>header===buffers[options.offset+index])
}
// 高阶函数,生成对应的函数
// 翻译验证对应的函数
// 比如说,生成检查 PNG 的函数
const isPNG = check([0x98,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a])

有一个检查类型的库:file-type

参考文章

作者(文章名称)连接
controZLhttps://juejin.cn/post/6915795898609975309
阿宝哥https://juejin.cn/post/6980142557066067982
你不知道的 Blobhttp://caibaojian.com/blob.html
苏苏同学https://juejin.cn/post/7046313942938812424
semlinkerhttps://mp.weixin.qq.com/s?__biz=MzI2MjcxNTQ0Nw==&mid=2247484317&idx=1&sn=c0b397b6bd5fdfced0c1bebc187a7c0d&scene=21
山月行谁说前端不需要懂二进制
northUCjs一张图搞定arrayBuffer/Blob/File/fileReader/canvas/base64的各种转换操作,以及文件上传
二十七刻前端下载文件与读取文件内容(多种类型的文件)